Spring Security | Note-11

Spring Security Note-11


Session管理

Session超时管理
1
server.session.timeout=10

在设置了超时时间之后,等待片刻,重新刷新页面;

实际上发现,我们依然能够获取到用户的信息,session并没有清除;

在SpringBoot的源码当中,我们可以发现,默认最少超时时间是一分钟,所以我们设置的时间,应该要大于60秒;

在某些情况下,我们需要提示用户session失效,让用户重新登录;

添加配置
1
2
3
4
5
6
7
8
@Override
protected void configure(HttpSecurity http) throws Exception {
applyPasswordAuthenticationConfig(http);
http.
...
.and().sessionManagement().invalidSessionUrl("/session/invalid")
...
}
1
2
3
4
5
6
7
8
9
@RestController
public class BrowserSecurityController {
@GetMapping("/session/invalid")
@ResponseStatus(code = HttpStatus.UNAUTHORIZED)
public SimpleResponse sessionInvalid() {
String message = "SESSION 失效";
return new SimpleResponse(message);
}
}
Session并发控制
添加配置
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@Override
protected void configure(HttpSecurity http) throws Exception {
applyPasswordAuthenticationConfig(http);
http.
...
.and().sessionManagement()
// session 过期返回URL
.invalidSessionUrl("/session/invalid")
// 最大session并发数量
.maximumSessions(1)
// false之后登录将会踢下之前的登录,true则是不允许之后登录
.maxSessionsPreventsLogin(false)
// 登录被踢掉时的自定义操作
.expiredSessionStrategy(new ImoocExpiredSessionStrategy())
.and()
// 默认
...
}
1
2
3
4
5
6
7
public class ImoocExpiredSessionStrategy implements SessionInformationExpiredStrategy {
@Override
public void onExpiredSessionDetected(SessionInformationExpiredEvent event) throws IOException, ServletException {
event.getResponse().setContentType("application/json;charset=UTF-8");
event.getResponse().getWriter().write("并发登录");
}
}
重构
1
2
3
4
5
6
7
8
public class SessionProperties {
// 最大session数 默认1
private int maximumSessions = 1;
// 并发登录的默认阻止设置 默认false
private boolean maxSessionsPreventsLogin;
// session失效的跳转地址
private String sessionInvalidUrl = SecurityConstants.DEFAULT_SESSION_INVALID_URL;
}
session失效以及并发控制的自定义的策略
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
public class AbstractSessionStrategy {
private final Logger logger = LoggerFactory.getLogger(getClass());
/**
* 跳转的url
*/
private String destinationUrl;
/**
* 重定向策略
*/
private RedirectStrategy redirectStrategy = new DefaultRedirectStrategy();
/**
* 跳转前是否创建新的session
*/
private boolean createNewSession = true;

private ObjectMapper objectMapper = new ObjectMapper();

public AbstractSessionStrategy(String invalidSessionUrl) {
Assert.isTrue(UrlUtils.isValidRedirectUrl(invalidSessionUrl), "url must start with '/' or with 'http(s)'");
this.destinationUrl = invalidSessionUrl;
}

protected void onSessionInvalid(HttpServletRequest request, HttpServletResponse response) throws IOException {

if (createNewSession) {
request.getSession();
}

String sourceUrl = request.getRequestURI();
String targetUrl;

if (StringUtils.endsWithIgnoreCase(sourceUrl, ".html")) {
targetUrl = destinationUrl + ".html";
logger.info("session失效,跳转到" + targetUrl);
redirectStrategy.sendRedirect(request, response, targetUrl);
} else {
String message = "session已失效";
if (isConcurrency()) {
message = message + ",有可能是并发登录导致的";
}
response.setStatus(HttpStatus.UNAUTHORIZED.value());
response.setContentType("application/json;charset=UTF-8");
response.getWriter().write(objectMapper.writeValueAsString(new SimpleResponse(message)));
}

}
protected boolean isConcurrency() {
return false;
}
public void setCreateNewSession(boolean createNewSession) {
this.createNewSession = createNewSession;
}
}
session超时的配置处理类
1
2
3
4
5
6
7
8
9
10
11
12
13
public class ImoocExpiredSessionStrategy extends AbstractSessionStrategy implements SessionInformationExpiredStrategy {
public ImoocExpiredSessionStrategy(String invalidSessionUrl) {
super(invalidSessionUrl);
}
@Override
public void onExpiredSessionDetected(SessionInformationExpiredEvent event) throws IOException, ServletException {
onSessionInvalid(event.getRequest(), event.getResponse());
}
@Override
protected boolean isConcurrency() {
return true;
}
}
session失效的配置处理类
1
2
3
4
5
6
7
8
9
10
public class ImoocInvalidSessionStrategy extends AbstractSessionStrategy implements InvalidSessionStrategy {
public ImoocInvalidSessionStrategy(String invalidSessionUrl) {
super(invalidSessionUrl);
}
@Override
public void onInvalidSessionDetected(HttpServletRequest request, HttpServletResponse response)
throws IOException, ServletException {
onSessionInvalid(request, response);
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@Override
protected void configure(HttpSecurity http) throws Exception {
applyPasswordAuthenticationConfig(http);
http.
...
.and().sessionManagement()
// session 过期返回URL
.invalidSessionStrategy(invalidSessionStrategy)
// 最大session并发数量
.maximumSessions(securityProperties.getBrowser().getSession().getMaximumSessions())
// false之后登录将会踢下之前的登录,true则是不允许之后登录
.maxSessionsPreventsLogin(securityProperties.getBrowser().getSession().isMaxSessionsPreventsLogin())
// 登录被踢掉时的自定义操作
.expiredSessionStrategy(expiredSessionStrategy)
.and()
// 默认
...
}
配置注入Bean
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@Configuration
public class BrowserSecurityBeanConfig {
@Autowired
private SecurityProperties securityProperties;
@Bean
@ConditionalOnMissingBean(InvalidSessionStrategy.class)
public InvalidSessionStrategy invalidSessionStrategy(){
return new ImoocInvalidSessionStrategy(securityProperties.getBrowser().getSession().getSessionInvalidUrl());
}

@Bean
@ConditionalOnMissingBean(SessionInformationExpiredStrategy.class)
public SessionInformationExpiredStrategy sessionInformationExpiredStrategy(){
return new ImoocExpiredSessionStrategy(securityProperties.getBrowser().getSession().getSessionInvalidUrl());
}
}

集群Session管理
问题:

面对一个问题,任何一个软件在面向用户使用的时候,至少部署两台机器;

在集群环境下,基于Session的认证就会存在问题;

用户的登录请求发送到Server1时,登录成功后,所有的session等信息,存储在Server1中;

在后续用户发送的服务请求中, 如果在负载均衡上没有做出处理的话,请求可能会发送到Server2中;

那么当后面的请求发送到Server2上,在Server2中不存在用户的session等信息认证,那么Server2将会拒绝用户的服务请求,需要再次登录;

解决方案:

将session抽取出来,放在一个独立的存取中,不放在服务器上;

在Browser添加依赖
1
2
3
4
<dependency>
<groupId>org.springframework.session</groupId>
<artifactId>spring-session</artifactId>
</dependency>

Spring Social提供的支持Session的类型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
public enum StoreType {
/**
* Redis backed sessions.
*/
REDIS,
/**
* Mongo backed sessions.
*/
MONGO,
/**
* JDBC backed sessions.
*/
JDBC,
/**
* Hazelcast backed sessions.
*/
HAZELCAST,
/**
* Simple in-memory map of sessions.
*/
HASH_MAP,
/**
* No session data-store.
*/
NONE;

}
配置
1
2
# SESSION
spring.session.store-type=redis

这样就能做到将session存储在redis中;

图形验证码

使ValidateCode实现Serializable接口,才能将图形验证码放入到session当中;

1
public class ValidateCode implements Serializable{}

不将图片放入session当中;

1
2
3
4
5
6
7
/**
* 保存校验码
*/
private void save(ServletWebRequest request, C validateCode) {
ValidateCode code = new ValidateCode(validateCode.getCode(),validateCode.getExpireTime());
sessionStrategy.setAttribute(request, getSessionKey(request), code);
}